#include <kstdio.h>
#include <Proc.h>
#include <BuddyAllocator.h>
#include <Elf.h>
#include <asm.h>
#include <PageTable.h>
#include <Segments.h>

SlabAllocator *threadAllocator;
SlabAllocator *procAllocator;
ProcManager *pm;


Proc::Proc()
{
	mm = new MemoryManager();
}

Proc::Proc(const Proc &prc)
					:fm(prc.fm)
{
	kprintf("Proc Copy Constructor\n");
	mm = new MemoryManager(*prc.mm);
	Link *lnk = prc._ready_threads[0].getFirst();
	Thread *old_thrd = get_obj(lnk, Thread, _tprio_list);
	Thread *new_thrd = new Thread(*old_thrd);
	new_thrd->setTid(getTid());
	new_thrd->setState(T_READY);
	new_thrd->setOwner(this);
	new_thrd->setPrio(0);
	threads.insBegin(&new_thrd->_siblings);
	_ready_threads[0].insBegin(&new_thrd->_tprio_list);
}

Thread * 
Proc::createThread(void (*func)(), uint32 esp)
{
	Thread *thrd = new Thread();
	uint32 *stk = thrd->getStackAddr();
	thrd->setTid(getTid());
	thrd->setState(T_READY);
	thrd->setPrio(0);
	thrd->setOwner(this);
	*(stk - 1) = USR_DATA_SEL;
	*(stk - 2) = esp;
	*(stk - 3) = setbit(0,9);
	*(stk - 4) = USR_CODE_SEL;
	*(stk - 5) = (uint32)func;
	kprintf("IP: %X\n", func);
	thrd->_esp = stk - 5;
	threads.insBegin(&thrd->_siblings);
	_ready_threads[0].insBegin(&thrd->_tprio_list);
	
	return thrd;
}

uint32 
Proc::createUserThread(reg_t *reg)
{
	//Проверить входит ли адрес стека (reg->ecx) в область стека процесса
	//Проверить входит ли адрес кода (reg->ebx) в область кода процесса
	Thread *thrd = new Thread();
	uint32 *stk = thrd->getStackAddr();
	thrd->setTid(getTid());
	thrd->setState(T_CREATE);
	thrd->setOwner(this);
	*(stk - 1) = USR_DATA_SEL;
	*(stk - 2) = reg->ecx;	//Адрес стека уровня пользователя
	*(stk - 3) = setbit(0,9);
	*(stk - 4) = USR_CODE_SEL;
	*(stk - 5) = reg->ebx;	//Адрес функции потока уровня пользователя
	thrd->_esp = stk - 5;
	threads.insBegin(&thrd->_siblings);
	return 0;
}

uint32 
Proc::startUserThread(reg_t *reg)
{
	Thread *thrd = getThreadByTid(reg->ebx);
	if(thrd && thrd->getState() == T_CREATE){
		_ready_threads[0].insBegin(&thrd->_tprio_list);
		thrd->setState(T_READY);
		return 0;
	}
	return 1;
}

Thread * 
Proc::getNextThread()
{
	uint32 i;
	for(i = 0; i < 32; i++){
		if(_ready_threads[i].getSize() > 0){
			kprintf("Proc _ready_threads[%i].size = %i\n", i, _ready_threads[i].getSize());
		}
	}
	kprintf("GetNextThread\n");
	Thread *thrd = NULL;
	if(_ready_threads[0].getSize()){
		Link *lnk = _ready_threads[0].pop();
		_ready_threads[0].insEnd(lnk);
		lnk = _ready_threads[0].getFirst();
		thrd = get_obj(lnk, Thread, _tprio_list);
		_ready_threads[0].remove(&thrd->_tprio_list);
	}
	return thrd;
}

uint32 
Proc::startThread(Thread *next)
{
	next->isFirst();
	asm("movl %0, %%esp"::"m" (next->_esp));
	asm("ret");
}

uint32 
Proc::switchThread(Thread *next)
{
	Thread *prev = current;
	saveContecst(prev);
	if(next->isFirst()){
		kprintf("*\n");
		retFirst(next);
	}
	restoreContecst(next);
}

Proc * 
ProcManager::getNextProc()
{
	uint32 i = 0;
	for(i = 0; i < 32; i++){
		//if(_ready_procs[i].getSize() > 0)
		//kprintf("_ready_procs[%i].size = %i ", i , _ready_procs[i].getSize());
	}
	//KSTOP;
	if(!_ready_procs[31].getSize()) return NULL;
	
	Link *lnk = _ready_procs[31].getHead()->next;
	Proc *prc;
	if(lnk != _ready_procs[31].getHead()){
		prc = get_obj(lnk, Proc, pprio_list);
		//kprintf("Next proc: %X\n", prc);
	}else{
		kprintf("Oops\n");
		KSTOP;
	}
	_ready_procs[31].remove(&prc->pprio_list);
	return prc;
}

uint32 
ProcManager::startInit()
{
	Proc *prc = getNextProc();
	/*Загрузить адресное пространство процесса Init*/
	Thread *thrd = prc->getNextThread();
	prc->startThread(thrd);
	
}

uint32 switchContecst(Thread *prev, Thread *next);

uint32 
ProcManager::schedule()
{
	Proc *pprc = current->getOwner();
	Proc *nprc = pprc;
	
	Thread *nthrd = NULL;
	if(pprc->getFlag(NEED_RESCHED_PROC)){
		pprc->clrFlag(NEED_RESCHED_PROC);
		nprc = getNextProc();
		if(!nprc){
			pprc->setTik(10);
			return 0;
		}	
		kprintf("PREV_PROC: %X, NEXT_PROC: %X\n", pprc, nprc); //KSTOP;
		if(pprc->getState() == P_RUNNING){
			//kprintf("CURRENT PROC IS RUNNING\n");
			pprc->setState(P_READY);
			_ready_procs[pprc->getPrio()].insEnd(&pprc->pprio_list);
			//KSTOP;
		}
		//KSTOP;
		nprc->setState(P_RUNNING);
		
		if(nprc->getTikSafe() == 0){
			
			nprc->setTik(10);
		}
		//KSTOP;
		//TODO:выделить новому процессу квант времени
	}
	//Переключение потоков
	nthrd = nprc->getNextThread();
	kprintf("NEXT THREAD: %X\n", nthrd);
	//KSTOP;
	if(!nthrd){
		
		//if(current->getState() != T_RUNNING){
			kprintf("KERROR: Proc have no ready threads\n");
			KSTOP;
		//}
	}else{
		current->setState(T_READY);
		pprc->_ready_threads[current->getPrio()].insEnd(&current->_tprio_list);
	}
	nthrd->setState(T_RUNNING);
	//im->intOff();asm volatile("cli");
	switchContecst(current, nthrd);
}

uint32 
switchContecst(Thread *prev, Thread *next)
{
	Proc *pprc = prev->getOwner();
	Proc *nprc = next->getOwner();
	bool first = false;
	
	saveContecst(prev);
	
	if(nprc != pprc){
		PageTable *gpd = nprc->mm->getGPD();
		pm->setGlobalPageTable((uint32 *)_pa(gpd->root));
		gpd->enablePaging();
	}
	pm->setKernelStackPointer(next->getStackAddr());
		
	if(next->isFirst()){
		kprintf("RestoreFirst\n");
		restoreFirst(next);
		sys_out();
		first = true;
	}else{
		kprintf("ResotreContecst\n");
		restoreContecst(next);
	}
	/*
	kprintf("ThreadAddr: %X, ThreadStackAddr: %X\n", next, next->getStackAddr());
	uint32 *stack = next->_esp;
	int32 z, n = 1;
	kprintf("stack addr: %X, stack_size: %i\n", stack, next->getStackAddr() - stack);
	//KSTOP;
	for(z = 0; z < next->getStackAddr() - stack; z++, n++){
		kprintf("s[%i] = %X | ", z, *(stack + z));
		if(n == 3){kprintf("\n"); n = 0;}
		//KSTOP;
	}
	*/
	/*
	uint32 esp;
	asm volatile("movl %%esp, %0":"=m" (esp):);
	kprintf("ESP: %X, SIZE: %i\n", esp, next->getStackAddr() - esp);
	kprintf("EAX: %i\n", next->getOwner()->getPid());
	//KSTOP;
	
	if(next->isFirst()){
		kprintf("first time\n");
		asm volatile("popl %fs	\n	\
			popl %es	\n	\
			popl %ds	\n	\
			popal	\n	\
			leave	\n	\
			iret");
		KSTOP;
		sys_out();
	}
	*/
	kprintf("Not first\n");
	//KSTOP;
}

uint32 
ProcManager::execInit(char *fname)
{
	//Создаем структуру Proc для процесса Init
	Proc *prc = new() Proc();
	prc->setPid(getPid());
	prc->setParent(NULL);
	prc->setPrio(31);
	prc->setState(P_RUNNING);
	
	
	//Добавляем процесс в списки
	all_procs.insBegin(&prc->all);
	
	kprintf("Proc Init Out GPD:\n");
	uint32 i;
	for(i = 0; i < 1024; i++){
		if(prc->mm->getGPD()->root[i] > 0){
			kprintf("$[%i] %X    ", i, prc->mm->getGPD()->root[i]);
		}
	}
	
	//Открываем файл init для последующего запуска
	uint32 dsd = prc->fm.fopen(fname);
	//Выделяем место под буфер. После реализации выделения памяти под массивы будет выделять ровно 128 байт. Пока целую страницу.
	char *buf = (char *) ((uint32)_va(zone_normal->allocFrame(0)));
	//Читаем первые 128 байт файла (ELF заголовок) в буфер
	prc->fm.fread(dsd, buf, 128);
	
	
	Elf *e_hdr = new Elf(buf);
	
	int z, R, W, X;
	Elf32_Phdr *e_phdr;
	uint32 prot;
	
	for(z = 0; z < e_hdr->getPHeaderNr();z++){
		prot = 0;
		e_phdr = e_hdr->getPHeader(z);
		
		if(e_phdr->p_type == 1){
			R = testbit(e_phdr->p_flags, 2);
			W = testbit(e_phdr->p_flags, 1);
			X = testbit(e_phdr->p_flags, 0);
			
			//kprintf("R: %i, W: %i, X: %i\n", R, W, X);
			if(X){
				prot = MA_PRIVATE;
				prc->mm->setCodeStart(e_phdr->p_vaddr);
				prc->mm->setCodeEnd(e_phdr->p_vaddr + e_phdr->p_filesz);
				prc->mmap((void *) e_phdr->p_vaddr, e_phdr->p_filesz, prot, 2, dsd, e_phdr->p_offset);
			}else{
				prot = MA_WRITE | MA_PRIVATE;
				
				prc->mm->setDataStart(e_phdr->p_vaddr);
				prc->mm->setDataEnd(e_phdr->p_vaddr + e_phdr->p_filesz);
				prc->mmap((void *) e_phdr->p_vaddr, e_phdr->p_memsz, prot, 2, dsd, e_phdr->p_offset);
				kprintf("DataStart: %X, DataEnd: %X\n", e_phdr->p_vaddr, e_phdr->p_vaddr + e_phdr->p_memsz);
				//KSTOP;
				if(e_phdr->p_memsz > e_phdr->p_filesz){
					prc->mm->setBssStart(e_phdr->p_vaddr + e_phdr->p_filesz);
					prc->mm->setBssEnd(prc->mm->getBssStart() + (e_phdr->p_memsz - e_phdr->p_filesz));
				}else{
					prc->mm->setBssStart(e_phdr->p_vaddr + e_phdr->p_memsz);
					prc->mm->setBssEnd(prc->mm->getBssStart());
				}
			}
		}
	}
	
	//Выделяем область памяти под пользовательский стек
	
	prot = MA_WRITE;
	prc->mmap((void *)0x3FF00000, 0x200000, prot, 2, NULL, 0);
	
	prc->mm->printMap();
	
	//Создаем поток
	Thread *thrd = prc->createThread((void (*)())e_hdr->getEntery(), 0x3FFFF000);
	thrd->isFirst();
	tss.esp0 = (uint32)thrd + 8192;
	tss.ss0 = SYS_DATA_SEL;
	tss.cr3 = (uint32)_pa(prc->mm->getGPD()->root);
	gdt->setDescriptor(16, SD_486TSS_AC, (uint32)&tss, 0x67, SPL);
	uint16 ts = TSS_SEL;
	asm("ltr %0"::"m" (ts));

	kprintf("NGPD:%X\n", prc->mm->getGPD()->root);
	
	prc->mm->getGPD()->enablePaging();
	
	kprintf("Proc Init Out GPD:\n");
	for(i = 0; i < 1024; i++){
		if(prc->mm->getGPD()->root[i] > 0){
			kprintf("$[%i] %X    ", i, prc->mm->getGPD()->root[i]);
		}
	}
	kprintf("\n");
	
	asm("movl %0, %%esp"::"m" (thrd->_esp));
	
	asm("movw $0x78, %ax");
	asm("movw %ax, %es;movw %ax, %fs;movw %ax, %gs;movw %ax, %ds;");
	asm("iret");
}

uint32 
ProcManager::clone(uint32 *eax)
{
	kprintf("Thread:%X\n", current);
	Proc *old_prc = current->getOwner();
	*eax = 0;
	//kprintf("BEFOR_CLONE\n");
	Proc *new_prc = new() Proc(*old_prc);
	//for(;;);
	new_prc->setPid(getPid());
	new_prc->setParent(old_prc);
	new_prc->setPrio(old_prc->getPrio());
	
	*eax = new_prc->getPid();
	
	//Добавляем процесс в списки
	all_procs.insBegin(&new_prc->all);
	_ready_procs[new_prc->getPrio()].insBegin(&new_prc->pprio_list);
	//im->intOff();
	old_prc->setFlag(NEED_RESCHED_PROC);
	//Thread *thrd = new_prc->getNextThread();
	//im->intOff();
	//Загружаем адресное пространство нового процесса
	//new_prc->mm->getGPD()->enablePaging();
	//setKernelStackPointer(thrd->getStackAddr());
	//Запускаем поток нового процесса
	//asm("movl %0, %%esp"::"m" (thrd->_esp));
	
	return 1;
}

uint32 
forkSysCall(reg_t *reg)
{
	kprintf("\nClone SysCall\n");
	//kprintf("\nSTOP\n");KSTOP;
	pm->clone(&reg->eax);
	return 1;
}

uint32 
threadCreateSysCall(reg_t *reg)
{
	Proc *prc = current->getOwner();
	prc->createUserThread(reg);
}

